#include "Statistics.h"

Relation::Relation(){
    numTuples = 0;
    IsJoinedRelation = false;
}

Relation::~Relation(){
    attributes.clear();
}

Statistics::Statistics(){
}

Statistics::Statistics(Statistics &copyMe){
	unordered_map<string, Relation*>::iterator r_it;
	for (r_it = copyMe.relations.begin(); r_it != copyMe.relations.end(); r_it++) {
		Relation *o = r_it->second;
		Relation *n = new Relation();
		n->numTuples = o->numTuples;
		n->IsJoinedRelation = o->IsJoinedRelation;
		n->relName = o->relName;

        for (set<string>::iterator it = o->joinedRelations.begin(); it != o->joinedRelations.end(); ++it)
            n->joinedRelations.insert(*it);

		unordered_map<string, int>::iterator a_it;
		for (a_it = o->attributes.begin(); a_it != o->attributes.end(); a_it++) {
			pair<string, int> ap(a_it->first, a_it->second);
			n->attributes.insert(ap);
		}
		pair<string, Relation*> rp(r_it->first, n);
		relations.insert(rp);
	}
}

Statistics::~Statistics(){
}

void Statistics::AddRel(char *relName, int numTuples){
    unordered_map<string, Relation*>::const_iterator it = relations.find(relName);

    if (it == relations.end()){
        Relation *r = new Relation;
		r->numTuples = numTuples;
		r->relName = relName;
		relations.insert(unordered_map<string, Relation*>::value_type(relName, r));
    }
    else{
        Relation *r = it->second;
		r->numTuples = numTuples;
    }
}

void Statistics::AddAtt(char *relName, char *attName, int numDistincts){
    unordered_map<string, Relation*>::const_iterator it = relations.find(relName);

	if (it == relations.end()){
		cout << "AddAtt(): relation " << relName << " not found!"<<endl;
		return;
	}
	else{
	    Relation *r = it->second;
		unordered_map<string, int>::const_iterator a_it = r->attributes.find(attName);

		if (a_it == r->attributes.end())
			r->attributes.insert(unordered_map<string, int>::value_type(attName, numDistincts));

		else{
			r->attributes.erase(attName);
            r->attributes.insert(unordered_map<string, int>::value_type(attName, numDistincts));
		}
	}
}

void Statistics::CopyRel(char *oldName, char *newName){
    unordered_map<string, Relation*>::const_iterator it = relations.find(oldName);

    if (it == relations.end()){
		cout << "CopyRel(): relation " << oldName << " not found!"<<endl;
		return;
	}
	else{
        Relation *r = it->second;
        Relation *nr = new Relation();

        nr->numTuples = r->numTuples;
        nr->relName = newName;
        nr->IsJoinedRelation = r->IsJoinedRelation;

        for (set<string>::iterator s_it = r->joinedRelations.begin(); s_it != r->joinedRelations.end(); ++s_it)
            nr->joinedRelations.insert(*s_it);

        for (unordered_map<string, int>::iterator it = r->attributes.begin(); it != r->attributes.end(); it++) {
            string name = nr->relName + "." + it->first;
            nr->attributes.insert(unordered_map<string, int>::value_type(name, it->second));
        }

        relations.insert(unordered_map<string, Relation*>::value_type(newName, nr));
	}
}

void Statistics::Read(char *fromWhere){
	FILE *file = fopen(fromWhere, "r");

	int numRelations = 0;
	fscanf(file, "%d", &numRelations);

    char temp[250];
	for (int i = 0; i < numRelations; i++) {
		Relation *r = new Relation();

		fscanf(file, "%s", temp);
		r->relName = temp;

		int isJoinedRelation = 0;
		fscanf(file, "%d", &(isJoinedRelation));
		r->IsJoinedRelation = isJoinedRelation;

		fscanf(file, "%lf", &(r->numTuples));

        if (r->IsJoinedRelation){
            int numJoins = 0;
            fscanf(file, "%d", &(numJoins));

            for (int j = 0; j < numJoins; j++) {
                fscanf(file, "%s", temp);
                r->joinedRelations.insert(temp);
            }
        }

		int numAttributes = 0;
		fscanf(file, "%d", &(numAttributes));

		for (int j = 0; j < numAttributes; j++) {
			fscanf(file, "%s", temp);
			int numDistincts;
			fscanf(file, "%d", &numDistincts);

            r->attributes.insert(unordered_map<string, int>::value_type(temp, numDistincts));
		}

        relations.insert(unordered_map<string, Relation*>::value_type(r->relName, r));
	}

	fclose(file);
}

void Statistics::Write(char *fromWhere){
    FILE *file = fopen(fromWhere, "w");

    fprintf(file, "%d ", relations.size());

	for (unordered_map<string, Relation*>::iterator it = relations.begin(); it != relations.end(); it++) {
		fprintf(file, "%s ", ((string) (it->first)).c_str());
		Relation *r = it->second;
		fprintf(file, "%d ", r->IsJoinedRelation);
		fprintf(file, "%lf ", r->numTuples);

		if (r->IsJoinedRelation){
		    fprintf(file, "%d ", r->joinedRelations.size());

            for (set<string>::iterator it = r->joinedRelations.begin(); it != r->joinedRelations.end(); ++it)
                fprintf(file, "%s ", ((string) (*it)).c_str());
		}


        fprintf(file, "%d ", r->attributes.size());
		for (unordered_map<string, int>::iterator a_it = r->attributes.begin(); a_it != r->attributes.end(); a_it++) {
			fprintf(file, "%s %d ", ((string) (a_it->first)).c_str(), a_it->second);
		}
	}

	fclose(file);
}

void Statistics::Apply(struct AndList *parseTree, char *relNames[], int numToJoin){
    if (IsValid(parseTree, relNames, numToJoin)){
        if (parseTree == NULL)
            ApplyCrossProduct(relNames, numToJoin);
        else
            ApplyJoinOrSelection(parseTree, relNames, numToJoin);
    }
}

int Statistics::GetAttributeInfo(string attribute, char *relNames[], int numToJoin, bool setLeft) {
	for (int i = 0; i < numToJoin; i++) {
		unordered_map<string, Relation*>::iterator r_it = relations.find(relNames[i]);

		unordered_map<string, int>::iterator a_it = r_it->second->attributes.find(attribute);
		if (a_it != r_it->second->attributes.end()){
		    if (setLeft)
                leftRelation = r_it->second;
            else
                rightRelation = r_it->second;

			return a_it->second;
		}
	}
	return 0;
}

RelData Statistics::GetAttributeInfo(string attribute, char *relNames[], int numToJoin) {
	RelData info = {""};
	for (int i = 0; i < numToJoin; i++) {
		unordered_map<string, Relation*>::iterator r_it = relations.find(relNames[i]);

		unordered_map<string, int>::iterator a_it = r_it->second->attributes.find(attribute);
		if (a_it != r_it->second->attributes.end()){
            info.name = relNames[i];
		}
	}
	return info;
}

void Statistics::ApplyCrossProduct(char *relNames[], int numToJoin){

    set<string> joinSet;
	double nOftuples = 1.0;

	//calculate number of tuples of cross product
	for (int i = 0; i < numToJoin; i++){
		set<string>::iterator it = joinSet.find(relNames[i]);
		if (it == joinSet.end()){
			unordered_map<string, Relation*>::iterator it = relations.find(relNames[i]);
			Relation *relation = it->second;
			nOftuples *= relation->numTuples;

			joinSet.insert(relNames[i]);

			if (relation->IsJoinedRelation){
				for (set<string>::iterator setIter = relation->joinedRelations.begin(); setIter != relation->joinedRelations.end(); setIter++)
					joinSet.insert(*setIter);
			}
		}
	}

	//update relations
	for (int i = 0; i < numToJoin; i++) {
		unordered_map<string, Relation*>::iterator it = relations.find(relNames[i]);
		Relation *rel = it->second;
		rel->IsJoinedRelation = true;
		rel->numTuples = nOftuples;

		for (set<string>::iterator setIter = joinSet.begin(); setIter != joinSet.end(); setIter++)
					rel->joinedRelations.insert(*setIter);
	}
}

void Statistics::ApplyJoinOrSelection(struct AndList *parseTree, char *relNames[], int numToJoin){
    struct AndList *_and = parseTree;
    while (_and != NULL){
        bool join = false;
        double factor1 = 1;
        double factor2 = 0;
        struct OrList *_or = _and->left;
        set<string> relationsInOr;
        Relation * R;

        while (_or != NULL){
            leftRelation = NULL;
            rightRelation = NULL;
            struct ComparisonOp *comp = _or->left;
            if (comp != NULL){
                struct Operand *operand = NULL;

                operand = comp->left;
                string attLeft = operand->value;
                int attCountL = GetAttributeInfo(operand->value, relNames,numToJoin, true);

                operand = comp->right;
                string attRight = operand->value;
                int attCountR = GetAttributeInfo(operand->value, relNames,numToJoin, false);

                if (comp->code == 1 || comp->code == 2){
                    if ((leftRelation == NULL && rightRelation != NULL) || (leftRelation != NULL && rightRelation == NULL)){
                        if (rightRelation == NULL){
                            R = leftRelation;
                            relationsInOr.insert(attLeft);
                        }
                        else{
                            R = rightRelation;
                            relationsInOr.insert(attRight);
                        }

                        factor1 *= 2.0 / 3.0;
                        factor2 += 1.0 / 3.0;
                    }
                }
                else if (comp->code == 3){
                    if ((leftRelation == NULL && rightRelation != NULL) || (leftRelation != NULL && rightRelation == NULL)){
                        if (rightRelation == NULL){
                            R = leftRelation;
                            relationsInOr.insert(attLeft);
                        }
                        else{
                            R = rightRelation;
                            relationsInOr.insert(attRight);
                        }

                        int count;
                        double numTuples;
                        if (leftRelation != NULL){
                            count = attCountL;
                            numTuples = leftRelation->numTuples;
                        }
                        else{
                            count = attCountR;
                            numTuples = rightRelation->numTuples;
                        }
                        if (count == -1){
                            factor1 *= (1.0 - (1.0 / numTuples));
                            factor2 += (1.0 / numTuples);
                        }
                        else{
                            factor1 *= (1.0 - (1.0 / count));
                            factor2 += (1.0 / count);
                        }
                    }
                    if (leftRelation != NULL && rightRelation != NULL){
                        join = true;
                        set<string> joins;
                        int lcount, rcount;

                        lcount = attCountL;
                        rcount = attCountR;

                        if (lcount == -1){
                            lcount = leftRelation->numTuples;
                        }
                        if (rcount == -1){
                            rcount = rightRelation->numTuples;
                        }

                        if (!leftRelation->IsJoinedRelation)
                            joins.insert(leftRelation->relName);
                        else{
                            for (set<string>::iterator it = leftRelation->joinedRelations.begin(); it != leftRelation->joinedRelations.end(); ++it)
                                joins.insert(*it);
                        }

                        if (!rightRelation->IsJoinedRelation)
                            joins.insert(rightRelation->relName);
                        else{
                            for (set<string>::iterator it = rightRelation->joinedRelations.begin(); it != rightRelation->joinedRelations.end(); ++it)
                                joins.insert(*it);
                        }

                        double newNumTuples = ((leftRelation->numTuples * rightRelation->numTuples) / (lcount > rcount ? lcount : rcount));
                        set<string>::iterator s_it;
                        for (s_it = joins.begin(); s_it != joins.end(); s_it++){
                            unordered_map<string, Relation*>::iterator r_it = relations.find(*s_it);
                            Relation *r1 = r_it->second;
                            r1->IsJoinedRelation = true;
                            r1->numTuples = newNumTuples;

                            for (set<string>::iterator it = joins.begin(); it != joins.end(); ++it)
                                r1->joinedRelations.insert(*it);
                        }
                    }
                }
            }
            _or = _or->rightOr;
        }

        if (!join) {
            factor1 = 1.0 - factor1;

            if (!R->IsJoinedRelation)
                R->numTuples *= relationsInOr.size() > 1 ? factor1 : factor2;
            else {
                for (set<string>::iterator it = R->joinedRelations.begin(); it != R->joinedRelations.end(); ++it){
                    unordered_map<string, Relation*>::iterator r_it = relations.find(*it);
                    Relation *rl = r_it->second;
                    rl->numTuples *= relationsInOr.size() > 1 ? factor1 : factor2;
                }
            }
        }

        _and = _and->rightAnd;
    }
}

bool Statistics::IsValid(struct AndList *parseTree, char ** relNames, int numToJoin){

	//if only one relation and no CNF then there is nothing to do
	if (numToJoin < 2 && parseTree == NULL)
		return false;

	//check that we are not asked to join partial sets
	set<string> expectedSet;
	for (int i = 0; i < numToJoin; i++) {
		unordered_map<string, Relation*>::iterator r_it = relations.find(relNames[i]);
		if (r_it == relations.end()) {
			cout << "Validation failed: Requested relation was not found in list of relations: " << relNames[i] << endl;
			return false;
		}

		Relation *relation = r_it->second;
		if (relation->IsJoinedRelation) {
			for (set<string>::iterator s_it = relation->joinedRelations.begin(); s_it != relation->joinedRelations.end(); s_it++)
				expectedSet.insert(*s_it);
		}
	}

	set<string> givenSet;
	for (int i = 0; i < numToJoin; i++)
		givenSet.insert(relNames[i]);

	for (set<string>::iterator s_it = expectedSet.begin(); s_it != expectedSet.end(); s_it++){
		set<string>::iterator setIter2 = givenSet.find(*s_it);
		if (setIter2 == givenSet.end()) {
			cout << "Validation failed: Expected relation was not found: " << *s_it << endl;
			return false;
		}
	}

	//check all attributes in CNF belong to a relation in the current statistics object
	struct AndList *_and = parseTree;
	while (_and != NULL){
		struct OrList *_or = _and->left;
		while (_or != NULL){
		    leftRelation = NULL;
			struct ComparisonOp *compOp = _or->left;
			if (compOp != NULL) {
				if (compOp->left->code == 4){
					GetAttributeInfo(compOp->left->value, relNames, numToJoin, true);
					if (leftRelation == NULL){
						cout << "Validation failed: left attribute in CNF was not found the relations to join: " << compOp->left->value << endl;
						return false;
					}
				}

                leftRelation = NULL;
				if (compOp->right->code == 4){
					GetAttributeInfo(compOp->right->value, relNames, numToJoin, true);
					if (leftRelation == NULL){
						cout << "Validation failed: right attribute in CNF was not found the relations to join: " << compOp->right->value << endl;
						return false;
					}
				}
			}
			_or = _or->rightOr;
		}
		_and = _and->rightAnd;
	}

	return true;
}

double Statistics::Estimate(struct AndList *parseTree, char **relNames, int numToJoin){
    Statistics s(*this);
    s.Apply(parseTree, relNames, numToJoin);

    unordered_map<string, Relation*>::iterator it = s.relations.find(relNames[0]);
    Relation *rl = it->second;

    return rl->numTuples;
}

